package com.example.webcrawler.service.mpl;

import com.example.webcrawler.model.Breakpoint;
import com.example.webcrawler.model.DebugEvent;
import com.example.webcrawler.model.DebugSession;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.microsoft.playwright.CDPSession;
import com.microsoft.playwright.Page;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.stream.Collectors;

@Service
public class DebugService {
    private static final Logger logger = LoggerFactory.getLogger(DebugService.class);

    // 存储CDP会话
    private final Map<String, CDPSession> cdpSessions = new ConcurrentHashMap<>();

    // 存储断点信息
    private final Map<String, List<Breakpoint>> breakpoints = new ConcurrentHashMap<>();

    // 存储调试会话状态
    private final Map<String, DebugSession> debugSessions = new ConcurrentHashMap<>();

    // 存储调试事件监听器
    private final Map<String, Consumer<DebugEvent>> eventListeners = new ConcurrentHashMap<>();

    // 存储WebSocket会话
    private final Map<String, DebugWebSocketHandler> webSocketHandlers = new ConcurrentHashMap<>();

    /**
     * 创建调试会话并连接到CDP
     */
    public String createDebugSession(Page page) {
        String sessionId = UUID.randomUUID().toString();
        try {
            CDPSession cdpSession = page.context().newCDPSession(page);
            cdpSessions.put(sessionId, cdpSession);

            // 启用调试域
            cdpSession.send("Debugger.enable");

            // 启用运行时域
            cdpSession.send("Runtime.enable");

            // 启用DOM域
            cdpSession.send("DOM.enable");

            // 注册事件处理器
            setupEventHandlers(cdpSession, sessionId);

            DebugSession debugSession = new DebugSession(sessionId, page, cdpSession);
            debugSessions.put(sessionId, debugSession);

            logger.info("Debug session created: {}", sessionId);
            notifyEvent(new DebugEvent(sessionId, "sessionCreated", "Debug session created"));
            return sessionId;
        } catch (Exception e) {
            logger.error("Failed to create debug session: {}", e.getMessage(), e);
            notifyEvent(new DebugEvent(sessionId, "sessionError", "Failed to create debug session: " + e.getMessage()));
            return null;
        }
    }

    /**
     * 设置事件处理器
     */
    private void setupEventHandlers(CDPSession cdpSession, String sessionId) {
        // JavaScript断点触发
        cdpSession.on("Debugger.paused", event -> {
            logger.info("Debugger paused: {}", event);
            handleDebuggerPaused(sessionId, event);
        });

        // 异常触发
        cdpSession.on("Runtime.exceptionThrown", event -> {
            logger.info("Exception thrown: {}", event);
            handleExceptionThrown(sessionId, event);
        });

        // DOM变化
        cdpSession.on("DOM.documentUpdated", event -> {
            logger.info("DOM document updated");
            handleDOMUpdated(sessionId, event);
        });

        // 断点解析
        cdpSession.on("Debugger.breakpointResolved", event -> {
            logger.info("Breakpoint resolved: {}", event);
            notifyEvent(new DebugEvent(sessionId, "breakpointResolved", event.toString()));
        });
    }

    /**
     * 处理调试器暂停事件
     */
    private void handleDebuggerPaused(String sessionId, JsonObject event) {
        DebugSession session = debugSessions.get(sessionId);
        if (session != null) {
            session.setPaused(true);
            session.setPauseReason(event.toString());
            session.setPauseData(event);
            // 通知前端调试器已暂停
            notifyEvent(new DebugEvent(sessionId, "paused", event.toString()));
        }
    }

    /**
     * 处理异常抛出事件
     */
    private void handleExceptionThrown(String sessionId, JsonObject event) {
        DebugSession session = debugSessions.get(sessionId);
        if (session != null) {
            // 通知前端异常发生
            notifyEvent(new DebugEvent(sessionId, "exception", event.toString()));
        }
    }

    /**
     * 处理DOM更新事件
     */
    private void handleDOMUpdated(String sessionId, JsonObject event) {
        DebugSession session = debugSessions.get(sessionId);
        if (session != null) {
            // 通知前端DOM已更新
            notifyEvent(new DebugEvent(sessionId, "domUpdated", event.toString()));
        }
    }

    /**
     * 设置JavaScript断点
     */
    public boolean setJavaScriptBreakpoint(String sessionId, String url, int lineNumber, int columnNumber, String condition) {
        try {
            CDPSession cdpSession = cdpSessions.get(sessionId);
            if (cdpSession == null) {
                notifyEvent(new DebugEvent(sessionId, "error", "CDP session not found"));
                return false;
            }

            JsonObject params = new JsonObject();
            params.addProperty("url", url);
            params.addProperty("lineNumber", lineNumber);
            params.addProperty("columnNumber", columnNumber);

            if (condition != null && !condition.isEmpty()) {
                params.addProperty("condition", condition);
            }

            JsonObject response = cdpSession.send("Debugger.setBreakpointByUrl", params);
            String breakpointId = response.get("breakpointId").getAsString();

            // 保存断点信息
            Breakpoint breakpoint = new Breakpoint(breakpointId, "javascript", url, lineNumber, columnNumber, condition);
            breakpoints.computeIfAbsent(sessionId, k -> new ArrayList<>()).add(breakpoint);

            logger.info("JavaScript breakpoint set: {}", breakpointId);
            notifyEvent(new DebugEvent(sessionId, "breakpointSet", "JavaScript breakpoint set: " + breakpointId));
            return true;
        } catch (Exception e) {
            logger.error("Failed to set JavaScript breakpoint: {}", e.getMessage(), e);
            notifyEvent(new DebugEvent(sessionId, "error", "Failed to set JavaScript breakpoint: " + e.getMessage()));
            return false;
        }
    }

    /**
     * 设置请求断点
     */
    public boolean setRequestBreakpoint(String sessionId, String urlPattern) {
        try {
            DebugSession session = debugSessions.get(sessionId);
            if (session == null) {
                notifyEvent(new DebugEvent(sessionId, "error", "Debug session not found"));
                return false;
            }

            // 通过拦截请求实现请求断点
            session.getPage().context().route(urlPattern, route -> {
                // 在这里暂停并等待用户操作
                logger.info("Request breakpoint hit for: {}", route.request().url());
                // 通知前端请求断点触发
                notifyEvent(new DebugEvent(sessionId, "requestBreakpointHit", route.request().url()));
                // 实际实现中，这里需要与前端交互，等待用户决定是否继续
                route.resume();
            });

            Breakpoint breakpoint = new Breakpoint(UUID.randomUUID().toString(), "request", urlPattern, 0, 0, null);
            breakpoints.computeIfAbsent(sessionId, k -> new ArrayList<>()).add(breakpoint);

            logger.info("Request breakpoint set for pattern: {}", urlPattern);
            notifyEvent(new DebugEvent(sessionId, "breakpointSet", "Request breakpoint set for pattern: " + urlPattern));
            return true;
        } catch (Exception e) {
            logger.error("Failed to set request breakpoint: {}", e.getMessage(), e);
            notifyEvent(new DebugEvent(sessionId, "error", "Failed to set request breakpoint: " + e.getMessage()));
            return false;
        }
    }

    /**
     * 设置DOM断点
     */
    public boolean setDOMBreakpoint(String sessionId, int nodeId, String type) {
        try {
            CDPSession cdpSession = cdpSessions.get(sessionId);
            if (cdpSession == null) {
                notifyEvent(new DebugEvent(sessionId, "error", "CDP session not found"));
                return false;
            }

            JsonObject params = new JsonObject();
            params.addProperty("nodeId", nodeId);
            params.addProperty("type", type); // "subtree-modified", "attribute-modified", "node-removed"

            JsonObject response = cdpSession.send("DOMDebugger.setDOMBreakpoint", params);

            String breakpointId = UUID.randomUUID().toString();
            Breakpoint breakpoint = new Breakpoint(breakpointId, "dom", String.valueOf(nodeId), 0, 0, type);
            breakpoints.computeIfAbsent(sessionId, k -> new ArrayList<>()).add(breakpoint);

            logger.info("DOM breakpoint set: {} on node {} with type {}", breakpointId, nodeId, type);
            notifyEvent(new DebugEvent(sessionId, "breakpointSet",
                    String.format("DOM breakpoint set: %s on node %d with type %s", breakpointId, nodeId, type)));
            return true;
        } catch (Exception e) {
            logger.error("Failed to set DOM breakpoint: {}", e.getMessage(), e);
            notifyEvent(new DebugEvent(sessionId, "error", "Failed to set DOM breakpoint: " + e.getMessage()));
            return false;
        }
    }

    /**
     * 设置异常断点
     */
    public boolean setExceptionBreakpoint(String sessionId, String type) {
        try {
            CDPSession cdpSession = cdpSessions.get(sessionId);
            if (cdpSession == null) {
                notifyEvent(new DebugEvent(sessionId, "error", "CDP session not found"));
                return false;
            }

            JsonObject params = new JsonObject();
            params.addProperty("state", type); // "none", "uncaught", "all"

            cdpSession.send("Debugger.setPauseOnExceptions", params);

            String breakpointId = UUID.randomUUID().toString();
            Breakpoint breakpoint = new Breakpoint(breakpointId, "exception", type, 0, 0, null);
            breakpoints.computeIfAbsent(sessionId, k -> new ArrayList<>()).add(breakpoint);

            logger.info("Exception breakpoint set: {}", type);
            notifyEvent(new DebugEvent(sessionId, "breakpointSet", "Exception breakpoint set: " + type));
            return true;
        } catch (Exception e) {
            logger.error("Failed to set exception breakpoint: {}", e.getMessage(), e);
            notifyEvent(new DebugEvent(sessionId, "error", "Failed to set exception breakpoint: " + e.getMessage()));
            return false;
        }
    }

    /**
     * 移除断点
     */
    public boolean removeBreakpoint(String sessionId, String breakpointId) {
        try {
            CDPSession cdpSession = cdpSessions.get(sessionId);
            if (cdpSession == null) {
                notifyEvent(new DebugEvent(sessionId, "error", "CDP session not found"));
                return false;
            }

            // 从列表中移除断点
            List<Breakpoint> sessionBreakpoints = breakpoints.get(sessionId);
            if (sessionBreakpoints != null) {
                sessionBreakpoints.removeIf(bp -> bp.getId().equals(breakpointId));
            }

            logger.info("Breakpoint removed: {}", breakpointId);
            notifyEvent(new DebugEvent(sessionId, "breakpointRemoved", "Breakpoint removed: " + breakpointId));
            return true;
        } catch (Exception e) {
            logger.error("Failed to remove breakpoint: {}", e.getMessage(), e);
            notifyEvent(new DebugEvent(sessionId, "error", "Failed to remove breakpoint: " + e.getMessage()));
            return false;
        }
    }

    /**
     * 启用断点
     */
    public boolean enableBreakpoint(String sessionId, String breakpointId) {
        try {
            List<Breakpoint> sessionBreakpoints = breakpoints.get(sessionId);
            if (sessionBreakpoints != null) {
                Optional<Breakpoint> breakpointOpt = sessionBreakpoints.stream()
                        .filter(bp -> bp.getId().equals(breakpointId))
                        .findFirst();
                if (breakpointOpt.isPresent()) {
                    Breakpoint breakpoint = breakpointOpt.get();
                    breakpoint.setEnabled(true);
                    logger.info("Breakpoint enabled: {}", breakpointId);
                    notifyEvent(new DebugEvent(sessionId, "breakpointEnabled", "Breakpoint enabled: " + breakpointId));
                    return true;
                }
            }
            return false;
        } catch (Exception e) {
            logger.error("Failed to enable breakpoint: {}", e.getMessage(), e);
            notifyEvent(new DebugEvent(sessionId, "error", "Failed to enable breakpoint: " + e.getMessage()));
            return false;
        }
    }

    /**
     * 禁用断点
     */
    public boolean disableBreakpoint(String sessionId, String breakpointId) {
        try {
            List<Breakpoint> sessionBreakpoints = breakpoints.get(sessionId);
            if (sessionBreakpoints != null) {
                Optional<Breakpoint> breakpointOpt = sessionBreakpoints.stream()
                        .filter(bp -> bp.getId().equals(breakpointId))
                        .findFirst();
                if (breakpointOpt.isPresent()) {
                    Breakpoint breakpoint = breakpointOpt.get();
                    breakpoint.setEnabled(false);
                    logger.info("Breakpoint disabled: {}", breakpointId);
                    notifyEvent(new DebugEvent(sessionId, "breakpointDisabled", "Breakpoint disabled: " + breakpointId));
                    return true;
                }
            }
            return false;
        } catch (Exception e) {
            logger.error("Failed to disable breakpoint: {}", e.getMessage(), e);
            notifyEvent(new DebugEvent(sessionId, "error", "Failed to disable breakpoint: " + e.getMessage()));
            return false;
        }
    }

    /**
     * 获取所有断点
     */
    public List<Breakpoint> getBreakpoints(String sessionId) {
        return breakpoints.getOrDefault(sessionId, new ArrayList<>());
    }

    /**
     * 恢复执行
     */
    public boolean resume(String sessionId) {
        try {
            CDPSession cdpSession = cdpSessions.get(sessionId);
            if (cdpSession == null) {
                notifyEvent(new DebugEvent(sessionId, "error", "CDP session not found"));
                return false;
            }

            cdpSession.send("Debugger.resume");

            DebugSession session = debugSessions.get(sessionId);
            if (session != null) {
                session.setPaused(false);
                session.setPauseReason(null);
                session.setPauseData(null);
            }

            logger.info("Debugger resumed for session: {}", sessionId);
            notifyEvent(new DebugEvent(sessionId, "resumed", "Debugger resumed"));
            return true;
        } catch (Exception e) {
            logger.error("Failed to resume debugger: {}", e.getMessage(), e);
            notifyEvent(new DebugEvent(sessionId, "error", "Failed to resume debugger: " + e.getMessage()));
            return false;
        }
    }

    /**
     * 单步执行
     */
    public boolean stepOver(String sessionId) {
        try {
            CDPSession cdpSession = cdpSessions.get(sessionId);
            if (cdpSession == null) {
                notifyEvent(new DebugEvent(sessionId, "error", "CDP session not found"));
                return false;
            }

            cdpSession.send("Debugger.stepOver");

            logger.info("Step over executed for session: {}", sessionId);
            notifyEvent(new DebugEvent(sessionId, "stepOver", "Step over executed"));
            return true;
        } catch (Exception e) {
            logger.error("Failed to step over: {}", e.getMessage(), e);
            notifyEvent(new DebugEvent(sessionId, "error", "Failed to step over: " + e.getMessage()));
            return false;
        }
    }

    /**
     * 进入函数
     */
    public boolean stepInto(String sessionId) {
        try {
            CDPSession cdpSession = cdpSessions.get(sessionId);
            if (cdpSession == null) {
                notifyEvent(new DebugEvent(sessionId, "error", "CDP session not found"));
                return false;
            }

            cdpSession.send("Debugger.stepInto");

            logger.info("Step into executed for session: {}", sessionId);
            notifyEvent(new DebugEvent(sessionId, "stepInto", "Step into executed"));
            return true;
        } catch (Exception e) {
            logger.error("Failed to step into: {}", e.getMessage(), e);
            notifyEvent(new DebugEvent(sessionId, "error", "Failed to step into: " + e.getMessage()));
            return false;
        }
    }

    /**
     * 跳出函数
     */
    public boolean stepOut(String sessionId) {
        try {
            CDPSession cdpSession = cdpSessions.get(sessionId);
            if (cdpSession == null) {
                notifyEvent(new DebugEvent(sessionId, "error", "CDP session not found"));
                return false;
            }

            cdpSession.send("Debugger.stepOut");

            logger.info("Step out executed for session: {}", sessionId);
            notifyEvent(new DebugEvent(sessionId, "stepOut", "Step out executed"));
            return true;
        } catch (Exception e) {
            logger.error("Failed to step out: {}", e.getMessage(), e);
            notifyEvent(new DebugEvent(sessionId, "error", "Failed to step out: " + e.getMessage()));
            return false;
        }
    }

    /**
     * 获取变量信息
     */
    public JsonObject getVariables(String sessionId, String callFrameId) {
        try {
            CDPSession cdpSession = cdpSessions.get(sessionId);
            if (cdpSession == null) {
                notifyEvent(new DebugEvent(sessionId, "error", "CDP session not found"));
                return new JsonObject();
            }

            JsonObject params = new JsonObject();
            if (callFrameId != null && !callFrameId.isEmpty()) {
                params.addProperty("callFrameId", callFrameId);
            } else {
                // 如果没有提供调用帧ID，使用作用域链中的第一个
                DebugSession session = debugSessions.get(sessionId);
                if (session != null && session.getPauseData() != null) {
                    // 从暂停数据中提取调用帧ID
                    JsonObject pauseData = session.getPauseData();
                    if (pauseData.has("callFrames")) {
                        // 获取第一个调用帧
                        JsonObject firstCallFrame = pauseData.getAsJsonArray("callFrames")
                                .get(0).getAsJsonObject();
                        if (firstCallFrame.has("callFrameId")) {
                            params.addProperty("callFrameId", firstCallFrame.get("callFrameId").getAsString());
                        }
                    }
                }
            }

            params.addProperty("ownProperties", true);
            params.addProperty("generatePreview", true);

            JsonObject response = cdpSession.send("Runtime.getProperties", params);
            logger.info("Variables retrieved for session: {}", sessionId);
            return response;
        } catch (Exception e) {
            logger.error("Failed to get variables: {}", e.getMessage(), e);
            notifyEvent(new DebugEvent(sessionId, "error", "Failed to get variables: " + e.getMessage()));
            return new JsonObject();
        }
    }

    /**
     * 获取调用栈信息
     */
    public List<String> getCallStack(String sessionId) {
        try {
            DebugSession session = debugSessions.get(sessionId);
            if (session != null && session.getPauseData() != null) {
                JsonObject pauseData = session.getPauseData();
                if (pauseData.has("callFrames")) {
                    JsonArray callFrames = pauseData.getAsJsonArray("callFrames");
                    List<String> callStack = new ArrayList<>();
                    for (int i = 0; i < callFrames.size(); i++) {
                        JsonObject frame = callFrames.get(i).getAsJsonObject();
                        StringBuilder frameInfo = new StringBuilder();

                        if (frame.has("functionName")) {
                            frameInfo.append(frame.get("functionName").getAsString());
                        } else {
                            frameInfo.append("(anonymous)");
                        }

                        frameInfo.append(" at ");

                        if (frame.has("url")) {
                            frameInfo.append(frame.get("url").getAsString());
                        }

                        if (frame.has("lineNumber")) {
                            frameInfo.append(":").append(frame.get("lineNumber").getAsInt() + 1);
                        }

                        callStack.add(frameInfo.toString());
                    }
                    return callStack;
                }
            }
            return new ArrayList<>();
        } catch (Exception e) {
            logger.error("Failed to get call stack: {}", e.getMessage(), e);
            notifyEvent(new DebugEvent(sessionId, "error", "Failed to get call stack: " + e.getMessage()));
            return new ArrayList<>();
        }
    }

    /**
     * 获取当前会话状态
     */
    public DebugSession getDebugSession(String sessionId) {
        return debugSessions.get(sessionId);
    }

    /**
     * 断开调试会话
     */
    public boolean detachSession(String sessionId) {
        try {
            CDPSession cdpSession = cdpSessions.get(sessionId);
            if (cdpSession != null) {
                cdpSession.detach();
                cdpSessions.remove(sessionId);
            }

            breakpoints.remove(sessionId);
            debugSessions.remove(sessionId);

            logger.info("Debug session detached: {}", sessionId);
            notifyEvent(new DebugEvent(sessionId, "sessionDetached", "Debug session detached"));
            return true;
        } catch (Exception e) {
            logger.error("Failed to detach debug session: {}", e.getMessage(), e);
            notifyEvent(new DebugEvent(sessionId, "error", "Failed to detach debug session: " + e.getMessage()));
            return false;
        }
    }

    /**
     * 注册事件监听器
     */
    public void addEventListener(String sessionId, Consumer<DebugEvent> listener) {
        eventListeners.put(sessionId, listener);
    }

    /**
     * 移除事件监听器
     */
    public void removeEventListener(String sessionId) {
        eventListeners.remove(sessionId);
    }

    /**
     * 注册WebSocket处理器
     */
    public void registerWebSocketHandler(String sessionId, DebugWebSocketHandler handler) {
        webSocketHandlers.put(sessionId, handler);
    }

    /**
     * 移除WebSocket处理器
     */
    public void removeWebSocketHandler(String sessionId) {
        webSocketHandlers.remove(sessionId);
    }

    /**
     * 通知事件
     */
    private void notifyEvent(DebugEvent event) {
        // 通过回调函数通知
        Consumer<DebugEvent> listener = eventListeners.get(event.getSessionId());
        if (listener != null) {
            try {
                listener.accept(event);
            } catch (Exception e) {
                logger.error("Error in debug event listener: {}", e.getMessage(), e);
            }
        }

        // 通过WebSocket通知
        DebugWebSocketHandler webSocketHandler = webSocketHandlers.get(event.getSessionId());
        if (webSocketHandler != null) {
            try {
                webSocketHandler.sendEvent(event);
            } catch (Exception e) {
                logger.error("Error sending event via WebSocket: {}", e.getMessage(), e);
            }
        }
    }

    /**
     * WebSocket处理器接口
     */
    public interface DebugWebSocketHandler {
        void sendEvent(DebugEvent event);
    }
}
